﻿namespace _4InARow
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    public class Board
    {
        public const string k_ErrorIllegalMove = "Unable to set square, illegal move";
        private const string k_ErrorOutsideBoard = "Unable to set square, it is outside the board";
        private const string k_ErrorSquareOccupied = "Unable to set square, it is occupied already";
        private const char k_EmptySquareChar = ' ';
        private const char k_Player1SquareChar = 'X';
        private const char k_Player2SquareChar = 'O';

        private uint m_Width;
        private uint m_Height;
        private eSquareStatus[,] m_Board;
        private List<Point> m_AvailableMoves;

        public enum eSquareStatus
        {
            Empty,
            Player1,
            Player2
        }

        public Board(uint i_Width, uint i_Height)
        {
            m_Width = i_Width;
            m_Height = i_Height;
            m_Board = new eSquareStatus[i_Width, i_Height];
            InitBoard();
        }

        public eSquareStatus[,] BoardMatrix
        {
            get 
            { 
                return m_Board; 
            }
        }

        public uint Width 
        { 
            get 
            { 
                return m_Width; 
            } 
        }

        public uint Height 
        { 
            get 
            { 
                return m_Height; 
            } 
        }

        public List<Point> AvailableMoves
        {
            get 
            { 
                return m_AvailableMoves; 
            }
        }

        public eSquareStatus GetSquareStatusByPlayerNumber(uint i_PlayerNumber)
        {
            eSquareStatus result = eSquareStatus.Empty;

            if (i_PlayerNumber == 1)
            {
                result = eSquareStatus.Player1;
            }
            else if (i_PlayerNumber == 2)
            {
                result = eSquareStatus.Player2;
            }

            return result;
        }

        public bool SetSquare(Player i_Player, uint i_Column, uint i_Row, out string o_MessageIfFailure)
        {
            o_MessageIfFailure = string.Empty;
            bool result = true;

            if (!checkNewMoveValidity(i_Column, i_Row, out o_MessageIfFailure))
            {
                result = false;
            }
            else
            {
                m_Board[i_Column - 1, i_Row - 1] = GetSquareStatusByPlayerNumber(i_Player.Number);
                updateAvailableMoves();
            }

            return result;
        }

        private bool checkNewMoveValidity(uint i_Column, uint i_Row, out string o_MessageIfFailure)
        {
            o_MessageIfFailure = string.Empty;
            bool result = true;

            if (i_Column > m_Width || i_Column == 0 || i_Row > m_Height || i_Row == 0)
            {
                o_MessageIfFailure = k_ErrorOutsideBoard;
                result = false;
            }
            else if (m_Board[i_Column - 1, i_Row - 1] != eSquareStatus.Empty)
            {
                o_MessageIfFailure = k_ErrorSquareOccupied;
                result = false;
            }
            else if (!pointExistsInAvailableMoves(i_Column, i_Row))
            {
                o_MessageIfFailure = k_ErrorIllegalMove;
                result = false;
            }

            return result;
        }

        public void EmptySquare(uint i_Width, uint i_Height)
        {
            m_Board[i_Width - 1, i_Height - 1] = eSquareStatus.Empty;
            updateAvailableMoves();
        }

        public char GetCharBySquare(uint i_Width, uint i_Height)
        {
            switch (m_Board[i_Width - 1, i_Height - 1])
            {
                case eSquareStatus.Empty:
                    return ' ';
                case eSquareStatus.Player1:
                    return 'X';
                case eSquareStatus.Player2:
                    return 'O';
                default:
                    return ' ';
            }
        }

        public bool IsFull()
        {
            return !(m_AvailableMoves.Count > 0);
        }

        public void InitBoard()
        {
            for (int i = 0; i < m_Width; i++)
            {
                for (int j = 0; j < m_Height; j++)
                {
                    m_Board[i, j] = eSquareStatus.Empty;
                }
            }

            updateAvailableMoves();
        }

        private void updateAvailableMoves()
        {
            m_AvailableMoves = new List<Point>();

            for (uint i = 0; i < m_Width; i++)
            {
                for (int j = (int)m_Height - 1; j >= 0; --j)
                {
                    if (m_Board[i, j] == eSquareStatus.Empty)
                    {
                        m_AvailableMoves.Add(new Point(i, (uint)j));
                        break;
                    }
                }
            }
        }

        private bool pointExistsInAvailableMoves(uint i_Width, uint i_Height)
        {
            return m_AvailableMoves.Exists(point => point.Equals(i_Width - 1, i_Height - 1));
        }
    }
}
